//////////////////////////////////////////////////////////////////////////
//
// This file is a part of APNS provider project.
// Copyright (c) Vladimir Gumenuk, 2013-2014.
//
// PROJECT:         APNS provider
// FILE:            Socket.cpp: implementation of the CGMSSocket class.
// AUTHOR:          Vladimir Gumenuk
// DATE:            22-Dec-2013
// MODIFICATIONS:
//
//////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "GMSSocket.h"
#include "GMSBuffer.h"

#pragma comment(lib, "WSock32.Lib")

#define LOGSOCERROR( x, y ) { LOG1(x, y); LogSocketError( y ); }

static void LogSocketError( DWORD dwErCode )
{
    LPCTSTR lpszDesc = NULL;
    
    switch( dwErCode )
    {
    case WSAEWOULDBLOCK    :  lpszDesc = _T("WSAEWOULDBLOCK    "); break;
    case WSAEINPROGRESS    :  lpszDesc = _T("WSAEINPROGRESS    "); break;
    case WSAEALREADY       :  lpszDesc = _T("WSAEALREADY       "); break;
    case WSAENOTSOCK       :  lpszDesc = _T("WSAENOTSOCK       "); break;
    case WSAEDESTADDRREQ   :  lpszDesc = _T("WSAEDESTADDRREQ   "); break;
    case WSAEMSGSIZE       :  lpszDesc = _T("WSAEMSGSIZE       "); break;
    case WSAEPROTOTYPE     :  lpszDesc = _T("WSAEPROTOTYPE     "); break;
    case WSAENOPROTOOPT    :  lpszDesc = _T("WSAENOPROTOOPT    "); break;
    case WSAEPROTONOSUPPORT:  lpszDesc = _T("WSAEPROTONOSUPPORT"); break;
    case WSAESOCKTNOSUPPORT:  lpszDesc = _T("WSAESOCKTNOSUPPORT"); break;
    case WSAEOPNOTSUPP     :  lpszDesc = _T("WSAEOPNOTSUPP     "); break;
    case WSAEPFNOSUPPORT   :  lpszDesc = _T("WSAEPFNOSUPPORT   "); break;
    case WSAEAFNOSUPPORT   :  lpszDesc = _T("WSAEAFNOSUPPORT   "); break;
    case WSAEADDRINUSE     :  lpszDesc = _T("WSAEADDRINUSE     "); break;
    case WSAEADDRNOTAVAIL  :  lpszDesc = _T("WSAEADDRNOTAVAIL  "); break;
    case WSAENETDOWN       :  lpszDesc = _T("WSAENETDOWN       "); break;
    case WSAENETUNREACH    :  lpszDesc = _T("WSAENETUNREACH    "); break;
    case WSAENETRESET      :  lpszDesc = _T("WSAENETRESET      "); break;
    case WSAECONNABORTED   :  lpszDesc = _T("WSAECONNABORTED   "); break;
    case WSAECONNRESET     :  lpszDesc = _T("WSAECONNRESET     "); break;
    case WSAENOBUFS        :  lpszDesc = _T("WSAENOBUFS        "); break;
    case WSAEISCONN        :  lpszDesc = _T("WSAEISCONN        "); break;
    case WSAENOTCONN       :  lpszDesc = _T("WSAENOTCONN       "); break;
    case WSAESHUTDOWN      :  lpszDesc = _T("WSAESHUTDOWN      "); break;
    case WSAETOOMANYREFS   :  lpszDesc = _T("WSAETOOMANYREFS   "); break;
    case WSAETIMEDOUT      :  lpszDesc = _T("WSAETIMEDOUT      "); break;
    case WSAECONNREFUSED   :  lpszDesc = _T("WSAECONNREFUSED   "); break;
    case WSAELOOP          :  lpszDesc = _T("WSAELOOP          "); break;
    case WSAENAMETOOLONG   :  lpszDesc = _T("WSAENAMETOOLONG   "); break;
    case WSAEHOSTDOWN      :  lpszDesc = _T("WSAEHOSTDOWN      "); break;
    case WSAEHOSTUNREACH   :  lpszDesc = _T("WSAEHOSTUNREACH   "); break;
    case WSAENOTEMPTY      :  lpszDesc = _T("WSAENOTEMPTY      "); break;
    case WSAEPROCLIM       :  lpszDesc = _T("WSAEPROCLIM       "); break;
    case WSAEUSERS         :  lpszDesc = _T("WSAEUSERS         "); break;
    case WSAEDQUOT         :  lpszDesc = _T("WSAEDQUOT         "); break;
    case WSAESTALE         :  lpszDesc = _T("WSAESTALE         "); break;
    case WSAEREMOTE        :  lpszDesc = _T("WSAEREMOTE        "); break;
    case WSASYSNOTREADY    :  lpszDesc = _T("WSASYSNOTREADY    "); break;
    case WSAVERNOTSUPPORTED:  lpszDesc = _T("WSAVERNOTSUPPORTED"); break;
    case WSANOTINITIALISED :  lpszDesc = _T("WSANOTINITIALISED "); break;
    case WSAHOST_NOT_FOUND :  lpszDesc = _T("WSAHOST_NOT_FOUND "); break;
    case WSATRY_AGAIN      :  lpszDesc = _T("WSATRY_AGAIN      "); break;
    case WSANO_RECOVERY    :  lpszDesc = _T("WSANO_RECOVERY    "); break;
    case WSANO_DATA        :  lpszDesc = _T("WSANO_DATA        "); break;
    default:                  lpszDesc = _T("UNKNOWN           "); break;           
    }
    LOG1( _T("Socket Error: 0x%x (%s)\n"), dwErCode, lpszDesc );
}

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CGMSSocket::CGMSSocket()
{
    m_hSocket = INVALID_SOCKET;
    m_bConnected = FALSE;
    memset( &m_sockAddr, 0, sizeof(m_sockAddr) );
}

CGMSSocket::~CGMSSocket()
{
    Disconnect();
}

void CGMSSocket::SetSocket( SOCKET hSocket )
{
    m_hSocket = hSocket;
    m_bConnected = TRUE;
}

HRESULT CGMSSocket::CreateSocket()
{
    HRESULT hr = S_OK;
    DWORD dwError = 0;

    if( m_hSocket == INVALID_SOCKET )
    {
        //creates a socket
        if( ( m_hSocket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ) ) == INVALID_SOCKET )
        {
            dwError = WSAGetLastError();
            LOGSOCERROR( _T(" --- Create socket failed!, error [%d]\n"), dwError );
            hr = HRESULT_FROM_WIN32( hr );
        }
        else // set receive/send time-out
        {
            int milisecs = CALL_TIMEOUT_MSEC;
            
            if( SOCKET_ERROR == setsockopt( m_hSocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&milisecs, sizeof(milisecs) ) ) 
            {
                dwError = WSAGetLastError();
                LOGSOCERROR( _T(" --- setsockopt (SO_RCVTIMEO) failed!, error [%d]\n"), dwError );
                closesocket( m_hSocket );
                m_hSocket = INVALID_SOCKET;
                hr = HRESULT_FROM_WIN32( hr );
            }
            else
            if( SOCKET_ERROR == setsockopt( m_hSocket, SOL_SOCKET, SO_SNDTIMEO, (char *)&milisecs, sizeof(milisecs) ) ) 
            {
                dwError = WSAGetLastError();
                LOGSOCERROR( _T(" --- setsockopt (SO_SNDTIMEO) failed!, error [%d]\n"), dwError );
                closesocket( m_hSocket );
                m_hSocket = INVALID_SOCKET;
                hr = HRESULT_FROM_WIN32( hr );
            }
        }
    }

    return hr;
}

STDMETHODIMP CGMSSocket::GetRemoteHostName( WCHAR szBuf[MAX_PATH] )
{
    if( m_csHostName.IsEmpty() )
        return E_UNEXPECTED;
    
    wcscpy( szBuf, m_csHostName );
    return S_OK;
}

STDMETHODIMP CGMSSocket::Connect( LPCOLESTR lpstrAddr, DWORD dwPort )
{
    if( !lpstrAddr )
        return E_POINTER;

    if( FAILED( CreateSocket() ) )
        return E_FAIL;

    USES_CONVERSION;
    DWORD dwError = 0;
    memset( &m_sockAddr, 0, sizeof( m_sockAddr ) );
    
    LPSTR lpszAscii = W2A( lpstrAddr );
    m_sockAddr.sin_family = AF_INET;
    m_sockAddr.sin_port = htons( (u_short)dwPort );
    m_sockAddr.sin_addr.s_addr = inet_addr(lpszAscii);
    if( m_sockAddr.sin_addr.s_addr == INADDR_NONE )
    {
        LPHOSTENT lphost;
        lphost = gethostbyname( lpszAscii );
        if( lphost != NULL )
        {
            m_csHostName = lphost->h_name;
            m_sockAddr.sin_addr.s_addr = ((LPIN_ADDR)lphost->h_addr)->s_addr;
        }
        else
        {
            dwError = WSAEINVAL;
            WSASetLastError( dwError );
            LOGSOCERROR( _T(" --- connect failed!, invealid address!, error: [%d]\n"), dwError );
            return HRESULT_FROM_WIN32( dwError );
        }
    }

    // try to connect with server
    if( connect( m_hSocket, (sockaddr*)&m_sockAddr, sizeof(m_sockAddr)) == SOCKET_ERROR )
    {
        dwError = WSAGetLastError();
        LOGSOCERROR( _T(" --- connect failed!, error [%d]\n"), dwError );
        return HRESULT_FROM_WIN32( dwError );
    }
    
    m_bConnected = TRUE;
    return S_OK;
}

STDMETHODIMP CGMSSocket::Disconnect()
{
    HRESULT hr = S_OK;

    if( m_hSocket != INVALID_SOCKET )
    {
        DWORD dwError = 0;
        if( shutdown( m_hSocket, 0x02 /*SD_BOTH*/ ) != 0 ) 
        {
            dwError = WSAGetLastError();
            LOGSOCERROR( _T(" --- shutdown failed!, error [%d]\n"), dwError );
            hr = HRESULT_FROM_WIN32( dwError );
        }

        if( closesocket( m_hSocket ) != 0 ) 
        {
            dwError = WSAGetLastError();
            LOGSOCERROR( _T(" --- closesocket failed!, error [%d]\n"), WSAGetLastError() );
            hr = HRESULT_FROM_WIN32( dwError );
        }
    }

    m_bConnected = FALSE;
    m_hSocket = INVALID_SOCKET;

    return hr;
}

HRESULT CGMSSocket::CreateListenSocket( DWORD dwPort )
{
    HRESULT hr = CreateSocket();
    if( FAILED( hr ) )
        return hr;

    memset( &m_sockAddr, 0, sizeof( m_sockAddr ) );
    
    m_sockAddr.sin_family = AF_INET;
    m_sockAddr.sin_addr.s_addr = INADDR_ANY;
    m_sockAddr.sin_port = htons( (u_short)dwPort );

    if( bind( m_hSocket, (struct sockaddr*)&m_sockAddr, sizeof( m_sockAddr ) ) == SOCKET_ERROR )
    {
        DWORD dwError = WSAGetLastError();
        LOGSOCERROR( _T(" --- bind failed!, error [%d]\n"), dwError );
        hr = HRESULT_FROM_WIN32( dwError );
    }

    return hr;
}

STDMETHODIMP CGMSSocket::Listen( IGMSNetClient **ppClient )
{
    if( !ppClient )
        return E_POINTER;
    if( m_hSocket == INVALID_SOCKET )
        return E_UNEXPECTED;

    HRESULT hr = E_FAIL;
    DWORD dwError = 0;

	if( listen( m_hSocket, SOMAXCONN ) != SOCKET_ERROR ) 
    {
        // accept the connection attempt
        int iAddrLen = sizeof( m_sockAddr );
        SOCKET hSockClient = accept( m_hSocket, (struct sockaddr*)&m_sockAddr, &iAddrLen );
        
        if( hSockClient != INVALID_SOCKET ) 
        {
            LOG2( _T(" --- accepted connection from %s\n"), inet_ntoa( m_sockAddr.sin_addr ) );
            TGMSSocketImpl *p = NULL;
            if( SUCCEEDED( hr = TGMSSocketImpl::CreateInstance( &p ) ) )
            {
                p->AddRef();
                p->SetSocket( hSockClient );
                if( FAILED( hr = p->QueryInterface( IID_IGMSNetClient, (void**)ppClient ) ) )
                {
                    LOGSOCERROR( _T(" --- accept - create IGMSNetClient failed!, error [0x%x]\n"), hr );
                }
                p->Release(); // if failed hr, this will delete object
            }
        }
        else
        {
            DWORD dwError = WSAGetLastError();
            LOGSOCERROR( _T(" --- accept failed!, error [%d]\n"), dwError );
        }
    }
    else
    {
        DWORD dwError = WSAGetLastError();
        LOGSOCERROR( _T(" --- listen failed!, error [%d]\n"), dwError );
    }

    if( dwError )
        hr = HRESULT_FROM_WIN32( dwError );

    return hr;
}

BOOL CGMSSocket::WaitForReceive( int iTimeOut )
{
    fd_set readSet;
    struct timeval tv;

    tv.tv_sec = iTimeOut;
    tv.tv_usec = 0;

    FD_ZERO( &readSet);
    FD_SET( m_hSocket, &readSet );

    if( select( -1, &readSet, NULL, NULL, &tv ) != 1 )
    {
        LOGSOCERROR( _T(" --- select failed!, error [%d]\n"), WSAGetLastError() );
        return FALSE;
    }

    return TRUE;
}

STDMETHODIMP_(int) CGMSSocket::SendRaw( LPBYTE pBuffer, DWORD dwSize )
{
    if( m_hSocket == INVALID_SOCKET || !IsConnected() )
        return SOCKET_ERROR;
    if( !pBuffer )
        return SOCKET_ERROR;
    if( dwSize == 0 )
        return SOCKET_ERROR;

    //LOG3( _T("* raw send enter\n") );

    int iRes = send( m_hSocket, (const char*)pBuffer, dwSize, 0 );
    if( iRes == SOCKET_ERROR )
    {
        DWORD dwError = WSAGetLastError();
        LOGSOCERROR( _T(" --- send failed!, error [%d]\n"), dwError );
    }

    //LOG3( _T("* raw send leave\n") );
    return iRes;
}

STDMETHODIMP_(int) CGMSSocket::ReceiveRaw( LPBYTE pBuffer, DWORD dwSize )
{
    if( m_hSocket == INVALID_SOCKET || !IsConnected() )
        return SOCKET_ERROR;
    if( !pBuffer )
        return SOCKET_ERROR;
    if( dwSize == 0 )
        return SOCKET_ERROR;

    //LOG3( _T("* raw recv enter\n") );
    
    int iRes = recv( m_hSocket, (char*)pBuffer, dwSize, 0 );
    if( iRes == SOCKET_ERROR )
    {
        DWORD dwError = WSAGetLastError();
        LOGSOCERROR( _T(" --- recv failed!, error [%d]\n"), dwError );
    }

    //LOG3( _T("* raw recv leave\n") );
    return iRes;
}

STDMETHODIMP CGMSSocket::SendBuffer( LPBYTE pBuffer, DWORD dwSize )
{
    if( m_hSocket == INVALID_SOCKET || !IsConnected() )
        return E_UNEXPECTED;
    if( !pBuffer )
        return E_POINTER;
    if( dwSize == 0 )
        return E_INVALIDARG;

    LOG3( _T("* send enter\n") );

    HRESULT hr = E_FAIL;
    
    int iRes = send( m_hSocket, (const char*)&dwSize, sizeof(DWORD), MSG_DONTROUTE );
    if( iRes != SOCKET_ERROR )
    {
        iRes = send( m_hSocket, (const char*)pBuffer, dwSize, MSG_DONTROUTE );
        if( iRes != SOCKET_ERROR ) 
        {
            hr = S_OK;
        }
    }

    if( FAILED( hr ) )
    {
        DWORD dwError = WSAGetLastError();
        LOGSOCERROR( _T(" --- send failed!, error [%d]\n"), dwError );
        hr = HRESULT_FROM_WIN32( dwError );
    }

    LOG3( _T("* send leave\n") );
    return hr;
}

STDMETHODIMP CGMSSocket::ReceiveBuffer( LPBYTE *ppBuffer, DWORD *pdwReceivedSize )
{
    if( m_hSocket == INVALID_SOCKET )
        return E_UNEXPECTED;
    if(  !ppBuffer || !pdwReceivedSize )
        return E_POINTER;

    if( !WaitForReceive( CALL_TIMEOUT_SEC ) )
    {
        LOG3( _T("* WaitForReceive -> no data!, return\n") );
        return E_GMS_TIMEOUT;
    }

    LOG3( _T("* recv enter\n") );
    DWORD dwError = 0;
    DWORD dwDataSize = 0;
    DWORD dwRead = recv( m_hSocket, (char*)&dwDataSize, sizeof(DWORD), 0 );
    if( dwRead == SOCKET_ERROR )
    {
        dwError = WSAGetLastError();
        LOGSOCERROR( _T("---- recv failed: [%d]!\n"), dwError );
        return HRESULT_FROM_WIN32( dwError );
    }

    if( dwRead == sizeof(DWORD) && dwDataSize != 0 )
    {
        LPBYTE pBuffer = new BYTE[ dwDataSize ];
        if( !pBuffer )
            return E_OUTOFMEMORY;
        
        DWORD dwLeft = dwDataSize;
        LPBYTE pBuf = pBuffer;

        while( dwLeft > 0 )
        {
            if( ( dwRead = recv( m_hSocket, (char*)pBuf, dwLeft, 0 ) ) == SOCKET_ERROR )
            {
                dwError = WSAGetLastError();
                LOGSOCERROR( _T("---- recv failed: [%d]!\n"), dwError );
                delete []pBuffer;
                return HRESULT_FROM_WIN32( dwError );
            }
            
            dwLeft -= dwRead;
            pBuf += dwRead;
        }
        
        *ppBuffer        = pBuffer;
        *pdwReceivedSize = dwDataSize;
        LOG3( _T("* recv leave\n") );
        return S_OK;
    }

    LOG1( _T("---- Receive failed -> invalid packet size!\n") );
    return E_GMS_INVALIDPACKETSIZE;
}

STDMETHODIMP_(DWORD) CGMSSocket::IsDataAvailable()
{
    if( m_hSocket == INVALID_SOCKET )
        return 0;

    DWORD dwNumBytes = 0;
    int iRet = ioctlsocket( m_hSocket, FIONREAD, &dwNumBytes );
    if( iRet == SOCKET_ERROR )
    {
        LOGSOCERROR( _T("---- recv failed: [%d]!\n"), WSAGetLastError() );
    }

    return dwNumBytes;
}

STDMETHODIMP_(BOOL) CGMSSocket::IsConnected()
{
    return m_bConnected;
}

STDMETHODIMP CGMSSocket::GetRemoteIP( ULONG *pulIP )
{
    return GetIP( false, pulIP );
}

STDMETHODIMP CGMSSocket::GetRemoteIP( char szBuf[NP_IP_SIZE] )
{
    return GetIP( false, szBuf );
}

STDMETHODIMP CGMSSocket::GetLocalIP( ULONG *pulIP )
{
    return GetIP( true, pulIP );
}

STDMETHODIMP CGMSSocket::GetLocalIP( char szBuf[NP_IP_SIZE] )
{
    return GetIP( true, szBuf );
}

HRESULT CGMSSocket::GetIP( bool bLocal, ULONG *pulIP )
{
    if( !pulIP )
        return E_POINTER;

    char szBuf[NP_IP_SIZE];
    
    HRESULT hr = GetIP( bLocal, szBuf );
    if( SUCCEEDED( hr ) )
    {
        long lIP = inet_addr( szBuf );
        if( lIP != INADDR_NONE )
        {
            *pulIP = lIP;
            hr = S_OK;
        }
        else
        {
            DWORD dwError = WSAGetLastError();
            LOGSOCERROR( _T(" --- GetIP failed!, error [%d]\n"), dwError );
            hr = HRESULT_FROM_WIN32( dwError );
        }
    }

    return hr;
}

HRESULT CGMSSocket::GetIP( bool bLocal, char szBuf[NP_IP_SIZE] )
{
    if( m_hSocket == INVALID_SOCKET )
        return E_UNEXPECTED;

    HRESULT hr = E_FAIL;
    sockaddr_in saddr;
    ZeroMemory( &saddr, sizeof(sockaddr_in) );
    int iLen = sizeof(sockaddr_in);

    int iRet;
    if( bLocal )
        iRet = getsockname( m_hSocket, (sockaddr*)&saddr, &iLen );
    else
        iRet= getpeername( m_hSocket, (sockaddr*)&saddr, &iLen );

    if( iRet != SOCKET_ERROR )
    {
        char *lpszIP = inet_ntoa( saddr.sin_addr );
        if( lpszIP )
        {
            strcpy( szBuf, lpszIP );
            hr = S_OK;
        }
    }

    if( FAILED( hr ) )
    {
        DWORD dwError = WSAGetLastError();
        LOGSOCERROR( _T(" --- GetIP failed!, error [%d]\n"), dwError );
        hr = HRESULT_FROM_WIN32( dwError );
    }

    return hr;
}

STDMETHODIMP CGMSSocket::Send( IGMSBuffer *pBuffer )
{
    if( !pBuffer )
        return E_POINTER;
    if( m_hSocket == INVALID_SOCKET || !IsConnected() )
        return E_UNEXPECTED;

    HRESULT hr = SendBuffer( pBuffer->GetPtr(), pBuffer->GetSize() );

    return hr;
}

STDMETHODIMP CGMSSocket::Receive( IGMSBuffer **ppBuffer )
{
    if( !ppBuffer )
        return E_POINTER;
    if( m_hSocket == INVALID_SOCKET || !IsConnected() )
        return E_UNEXPECTED;

    LPBYTE pBuf = NULL;
    DWORD dwSize = 0;
    
    HRESULT hr = ReceiveBuffer( &pBuf, &dwSize );
    if( SUCCEEDED( hr ) )
    {
        CComPtr<IGMSBuffer> spBufOut;
        if( SUCCEEDED( hr = CGMSBuffer::Create( &spBufOut ) ) )
        {
            if( SUCCEEDED( hr = spBufOut->Attach( pBuf, dwSize ) ) )
            {
                pBuf = NULL; // now the IGMSBuffer is responsible for this memory block
                *ppBuffer = spBufOut.Detach();
            }
        }

        SAFE_DELETE_ARRAY( pBuf );
    }

    return hr;
}

//////////////////////////////////////////////////////////////////////////
BOOL CGMSNetwork::m_bNetOk = FALSE;

CGMSNetwork::CGMSNetwork()
{

}
 
CGMSNetwork::~CGMSNetwork()
{
    
}

STDMETHODIMP CGMSNetwork::InitNetLayer()
{
    if( m_bNetOk )
        return S_FALSE;

    WSADATA wsaData;

    WORD wVersionRequested = MAKEWORD(2,0);
    int nResult = WSAStartup( wVersionRequested, &wsaData );
    if( nResult != 0 )
    {
        LOGSOCERROR( _T("InitNetworkLayer -> WSAStartup FAILED!, error: [%d]\n"), nResult );
        return HRESULT_FROM_WIN32( nResult );
    }
    
    if( LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0 )
    {
        LOG1( _T("FATAL: Unknown version of socket API!\n") );
        WSACleanup();
        return E_FAIL;
    }

    LOG1( _T(" --- Network layer initialized OK\n") );

    m_bNetOk = TRUE;
    return m_bNetOk ? S_OK : E_FAIL;
}

STDMETHODIMP CGMSNetwork::ShutNetLayer()
{
    HRESULT hr = E_UNEXPECTED;

    if( m_bNetOk )
    {
        int nError = WSACleanup();
        if( nError != 0 )
        {
            LOGSOCERROR( _T("UninitNetworkLayer -> WSACleanup() failed!, error [%d]\n"), nError );
            hr = HRESULT_FROM_WIN32( nError );
        }
        else
        {
            m_bNetOk = FALSE;
            LOG1( _T(" --- Network layer uninitialized OK\n") );
            hr = S_OK;
        }
    }

    return hr;
}

STDMETHODIMP CGMSNetwork::CreateNetClient( IGMSNetClient **ppClient )
{
    if( !m_bNetOk )
        return E_UNEXPECTED;
    if( !ppClient )
        return E_POINTER;

    TGMSSocketImpl *p = NULL;
    HRESULT hr = TGMSSocketImpl::CreateInstance( &p );
    if( SUCCEEDED( hr ) )
    {
        p->AddRef();

        if( SUCCEEDED( hr = p->CreateSocket() ) )
        {
            hr = p->QueryInterface( IID_IGMSNetClient, (void**)ppClient );
        }

        p->Release();
    }

    return hr;
}

STDMETHODIMP CGMSNetwork::CreateNetListener( DWORD dwPort, IGMSNetListener **ppListener )
{
    if( !m_bNetOk )
        return E_UNEXPECTED;
    if( !ppListener )
        return E_POINTER;

    TGMSSocketImpl *p = NULL;
    HRESULT hr = TGMSSocketImpl::CreateInstance( &p );
    if( SUCCEEDED( hr ) )
    {
        p->AddRef();

        if( SUCCEEDED( hr = p->CreateListenSocket( dwPort ) ) )
        {
            hr = p->QueryInterface( IID_IGMSNetListener, (void**)ppListener );
        }

        p->Release();
    }

    return hr;
}

HRESULT __stdcall CreateGMSNetwork( IGMSNetwork **ppNet )
{
    if( !ppNet )
        return E_POINTER;

    TGMSNetworkImpl *p = NULL;
    
    HRESULT hr = TGMSNetworkImpl::CreateInstance( &p );
    if( SUCCEEDED( hr ) )
    {
        p->AddRef();

        hr = p->QueryInterface( IID_IGMSNetwork, (void**)ppNet );

        p->Release();
    }

    return hr;
}
